﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web.Script.Serialization;
using System.Text;

using Mvc.XForms.Utilities;
using System.Web;

namespace Mvc.XForms.Controls.Formatters {
	public class DefaultFormatter : IFormatter {
		#region Core

		private const string controlFormat = "<div{2} class=\"{0}\">{1}</div>";
		private const string clientIDFormat = " id=\"{0}\"";
		private const string labelFormat = "<label for=\"{0}\">{1}{2}</label>";
		private const string hintFormat = " title=\"{0}\"";
		private const string helpFormat = "<span class=\"help\" title=\"Help|{0}\"><img src=\"/images/help.png\"/></span>";
		private const string alertFormat = "<span class=\"alert\">{0}</span>";
		private const string readOnlyFormat = " readonly=\"readonly\"";
		private const string requiredFormat = "<span>*</span>";
		private const string attributeFormat = " {0}=\"{1}\"";

		public virtual string Control(ICoreControl control, StringBuilder controlBody) {
			controlBody.Append(Help(control));
			controlBody.Append(Alert(control));

			var controlStr = string.Format(controlFormat, control.CssClass, controlBody, ClientID(control));
			return controlStr;
		}

		public virtual string ClientID(ICoreControl control) {
			if (string.IsNullOrEmpty(control.ClientID))
				return string.Empty;

			return string.Format(clientIDFormat, control.ClientID);
		}

		public virtual string Label(ICoreControl control) {
			if (string.IsNullOrEmpty(control.Label))
				return string.Empty;

			return string.Format(labelFormat, control.FullID, control.Label, Required(control));
		}

		public virtual string Hint(ICoreControl control) {
			if (string.IsNullOrEmpty(control.Hint))
				return string.Empty;

			return string.Format(hintFormat, control.Hint);
		}

		public virtual string Help(ICoreControl control) {
			if (string.IsNullOrEmpty(control.Help))
				return string.Empty;

			return string.Format(helpFormat, control.Help);
		}

		public virtual string Alert(ICoreControl control) {
			if (string.IsNullOrEmpty(control.Alert))
				return string.Empty;

			return string.Format(alertFormat, control.Alert);
		}

		public virtual string Readonly(ICoreControl control) {
			if (!control.ReadOnly)
				return string.Empty;

			return readOnlyFormat;
		}

		public virtual string Required(ICoreControl control) {
			if (!control.Required)
				return string.Empty;

			return requiredFormat;
		}

		public virtual string Attributes(IXFormControl control) {
			var attributes = new StringBuilder();
			control.Attributes.ForEach(attr => {
				attributes.AppendFormat(attributeFormat, attr.Key, attr.Value);
			});

			return attributes.ToString();
		}

		#endregion

		#region XForm

		private const string xformBeginFormat = "<form action=\"{0}\" method=\"{1}\"{2} class=\"{3}\">";
		private const string xformEndFormat = "</form>";
		private const string xformScript = "<script type=\"text/javascript\">$(document).ready(function(){{mvc.xforms.init({0})}});</script>";

		public string XFormBegin(IXForm xform) {
			var enctype = xform.DefaultSubmission.Method == SubmissionMethod.FormDataPost ?
				" enctype=\"multipart/form-data\"" : string.Empty;

			var submissionMethod = "post";
			if (xform.DefaultSubmission.Method == SubmissionMethod.Get)
				submissionMethod = "get";

			var xformBegin = string.Format(xformBeginFormat, xform.DefaultSubmission.Resource, submissionMethod, enctype, xform.CssClass);
			return xformBegin;
		}

		public string XFormEnd(IXForm xform) {
			return xformEndFormat;
		}

		public string XFormScript(IXForm xform) {
			var controlsJson = xform.ToJson();

#pragma warning disable 0618
			var serializer = new JavaScriptSerializer();
			var json = serializer.Serialize(controlsJson);
#pragma warning restore 0618

			return string.Format(xformScript, json);
		}

		#endregion

		#region Group

		private const string groupBeginFormat = "<{0} id=\"{1}\" class=\"{2}\"{3}>";
		private const string groupEndFormat = "</{0}>";

		public string GroupBegin(IGroup group) {
			return string.Format(groupBeginFormat, group.Tag, group.FullID, group.CssClass, Attributes(group));
		}

		public string GroupEnd(IGroup group) {
			return string.Format(groupEndFormat, group.Tag);
		}

		#endregion

		#region Input

		private const string inputTextFormat = "<input id=\"{0}\" type=\"text\" name=\"{0}\" value=\"{1}\"{2}/>";
		private const string hiddenFormat = "<input id=\"{0}\" type=\"hidden\" name=\"{0}\" value=\"{1}\"/>";
		private const string uploadFormat = "<input id=\"{0}\" type=\"file\" name=\"{0}\" value=\"{1}\"{2}/>";
		private const string secretFormat = "<input id=\"{0}\" type=\"password\" name=\"{0}\" value=\"{1}\"{2}/>";
		private const string inputBooleanFormat = "<input id=\"{0}\" type=\"checkbox\" name=\"{0}\" value=\"true\"{1}/><input type=\"hidden\" name=\"{0}\" value=\"false\"/>";
		private const string inputBooleanTrueFormat = "<input id=\"{0}\" type=\"checkbox\" name=\"{0}\" value=\"true\" checked=\"checked\"{1}/><input type=\"hidden\" name=\"{0}\" value=\"false\"/>";

		public virtual string InputText(IInput input) {
			var controlBody = new StringBuilder();

			var attributes = string.Concat(Hint(input), Readonly(input));
			controlBody.Append(Label(input));
			controlBody.AppendFormat(inputTextFormat, input.FullID, input.EncodedValue, attributes);

			var control = Control(input, controlBody);
			return control;
		}

		public virtual string InputBoolean(IInput input) {
			var controlBody = new StringBuilder();
			var attributes = Hint(input);

			var format = inputBooleanFormat;
			if (input.Value != null) {
				bool inputVal = false;
				bool.TryParse(input.Value.ToString(), out inputVal);
				if (inputVal)
					format = inputBooleanTrueFormat;
			}

			controlBody.Append(Label(input));
			controlBody.AppendFormat(format, input.FullID, attributes);

			var control = Control(input, controlBody);
			return control;
		}

		public virtual string InputDate(IInput input) {
			if (input.Value != null) {
				DateTime inputVal;
				if (DateTime.TryParse(input.Value.ToString(), out inputVal))
					input.EncodedValue = inputVal.ToString("d");
				else
					input.EncodedValue = HttpUtility.HtmlEncode(input.Value.ToString());
			}

			var control = InputText(input);
			return control;
		}

		public virtual string Hidden(IInput hidden) {
			var control = string.Format(hiddenFormat, hidden.FullID, hidden.EncodedValue);
			return control;
		}

		public virtual string Secret(IInput secret) {
			var controlBody = new StringBuilder();

			var attributes = string.Concat(Hint(secret), Readonly(secret));
			controlBody.Append(Label(secret));
			controlBody.AppendFormat(secretFormat, secret.FullID, secret.EncodedValue, attributes);

			var control = Control(secret, controlBody);
			return control;
		}

		public virtual string Upload(IInput upload) {
			var controlBody = new StringBuilder();

			var attributes = string.Concat(Hint(upload), Readonly(upload));
			controlBody.Append(Label(upload));
			controlBody.AppendFormat(uploadFormat, upload.FullID, upload.EncodedValue, attributes);

			var control = Control(upload, controlBody);
			return control;
		}

		#endregion

		#region TextArea

		private const string textAreaFormat = "<textarea id=\"{0}\" name=\"{0}\" rows=\"{3}\" cols=\"{4}\"{2}>{1}</textarea>";

		public virtual string TextArea(ITextArea textArea) {
			var controlBody = new StringBuilder();

			var attributes = string.Concat(Hint(textArea), Readonly(textArea));
			controlBody.Append(Label(textArea));
			controlBody.AppendFormat(textAreaFormat, textArea.FullID, textArea.EncodedValue, attributes, textArea.Rows, textArea.Columns);

			var control = Control(textArea, controlBody);
			return control;
		}

		#endregion

		#region Select

		private const string selectFormat = "<input type=\"hidden\" name=\"{0}\" value=\"{2}\"/><select id=\"{0}\" name=\"{0}\" multiple=\"multiple\"{1}>";
		private const string selectEndFormat = "</select>";
		private const string optionFormat = "<option value=\"{0}\">{1}</option>";
		private const string optionSelectedFormat = "<option value=\"{0}\" selected=\"selected\">{1}</option>";
		private const string selectCheckBoxControlFormat = "<div id=\"{0}\"{1}><input type=\"hidden\" name=\"{0}\" value=\"{2}\"/>";
		private const string selectCheckBoxControlEndFormat = "</div>";
		private const string selectCheckBoxFormat = "<div><input id=\"{2}\" type=\"checkbox\" name=\"{3}\" value=\"{0}\"/><label for=\"{2}\">{1}</label></div>";
		private const string selectCheckBoxCheckedFormat = "<div><input id=\"{2}\" type=\"checkbox\" name=\"{3}\" value=\"{0}\" checked=\"checked\"/><label for=\"{2}\">{1}</label></div>";

		public string Select(ISelect select) {
			var controlBody = new StringBuilder();

			var attributes = string.Concat(Hint(select), Readonly(select));
			controlBody.Append(Label(select));
			controlBody.AppendFormat(selectFormat, select.FullID, attributes, select.ValueSelector);
			select.SelectList.ForEach((li, i) => {
				var format = li.Selected ? optionSelectedFormat : optionFormat;
				controlBody.AppendFormat(format, li.Value, li.Text);
			});
			controlBody.Append(selectEndFormat);

			var control = Control(select, controlBody);
			return control;
		}

		public string SelectFull(ISelect select) {
			var controlBody = new StringBuilder();

			var attributes = string.Concat(Hint(select), Readonly(select));
			controlBody.Append(Label(select));
			controlBody.AppendFormat(selectCheckBoxControlFormat, select.FullID, attributes, select.ValueSelector);

			select.SelectList.ForEach((li, i) => {
				var format = li.Selected ? selectCheckBoxCheckedFormat : selectCheckBoxFormat;
				controlBody.AppendFormat(format, li.Value, li.Text, select.ID + "-" + i, select.ID);
			});

			controlBody.Append(selectCheckBoxControlEndFormat);

			var control = Control(select, controlBody);
			return control;
		}

		#endregion

		#region Select1

		private const string select1Format = "<select id=\"{0}\" name=\"{0}\"{1}>";
		private const string select1NonRelevantFormat = "<select id=\"{0}\"{1}>";
		private const string select1EndFormat = "</select>";
		private const string select1RadioControlFormat = "<div id=\"{0}\"{1}>";
		private const string select1RadioControlEndFormat = "</div>";
		private const string select1RadioFormat = "<div><input type=\"radio\" id=\"{2}\" name=\"{3}\" value=\"{0}\" /><label for=\"{2}\">{1}</label></div>";
		private const string select1RadioNonRelevantFormat = "<div><input type=\"radio\" id=\"{2}\" value=\"{0}\" /><label for=\"{2}\">{1}</label></div>";
		private const string select1RadioCheckedFormat = "<div><input type=\"radio\" id=\"{2}\" name=\"{3}\" value=\"{0}\" checked=\"checked\" /><label for=\"{2}\">{1}</label></div>";
		private const string select1RadioCheckedNonRelevantFormat = "<div><input type=\"radio\" id=\"{2}\" value=\"{0}\" checked=\"checked\" /><label for=\"{2}\">{1}</label></div>";

		public string Select1(ISelect1 select1) {
			var controlBody = new StringBuilder();

			var format = select1.Relevant ? select1Format : select1NonRelevantFormat;

			var attributes = string.Concat(Hint(select1), Readonly(select1));
			controlBody.Append(Label(select1));
			controlBody.AppendFormat(format, select1.FullID, attributes);
			select1.SelectList.ForEach((li, i) => {
				var optFormat = li.Selected ? optionSelectedFormat : optionFormat;
				controlBody.AppendFormat(optFormat, li.Value, li.Text);
			});
			controlBody.Append(select1EndFormat);

			var control = Control(select1, controlBody);
			return control;
		}

		public string Select1Full(ISelect1 select1) {
			var controlBody = new StringBuilder();

			var attributes = string.Concat(Hint(select1), Readonly(select1));
			controlBody.Append(Label(select1));
			controlBody.AppendFormat(select1RadioControlFormat, select1.FullID, attributes);
			select1.SelectList.ForEach((li, i) => {
				var format = li.Selected ?
					(select1.Relevant ? select1RadioCheckedFormat : select1RadioCheckedNonRelevantFormat) :
					(select1.Relevant ? select1RadioFormat : select1RadioNonRelevantFormat);
				controlBody.AppendFormat(format, li.Value, li.Text, select1.FullID + "-" + i, select1.FullID);
			});
			controlBody.Append(select1RadioControlEndFormat);

			var control = Control(select1, controlBody);
			return control;
		}

		#endregion

		#region Repeat

		// Repeat requires hidden field for when it is empty
		private const string repeatBeginFormat = "<div><input type=\"hidden\" name=\"{1}\"/></div><{0} id=\"{1}\" class=\"{2}\">";
		private const string repeatBeginNonRelevantFormat = "<{0} id=\"{1}\" class=\"{2}\">";
		private const string repeatEndFormat = "</{0}>";
		private const string repeatItemBeginFormat = "<{0}>";
		private const string repeatItemEndFormat = "</{0}>";

		public string RepeatBegin(IRepeat repeat) {
			// ** DESERIALIZER NEEDS UPDATED TO ACCOMMODATE EMPTY LISTS **
			// ** UNITL THEN NO HIDDEN FIELD **
			//var format = repeat.Relevant ? repeatBeginFormat : repeatBeginNonRelevantFormat;
			//return string.Format(format, repeat.Tag, repeat.FullID, repeat.CssClass);

			return string.Format(repeatBeginNonRelevantFormat, repeat.Tag, repeat.FullID, repeat.CssClass);
		}

		public string RepeatEnd(IRepeat repeat) {
			return string.Format(repeatEndFormat, repeat.Tag);
		}

		public string RepeatItemBegin(IRepeat repeat) {
			return string.Format(repeatItemBeginFormat, repeat.ItemTag);
		}

		public string RepeatItemEnd(IRepeat repeat) {
			return string.Format(repeatItemEndFormat, repeat.ItemTag);
		}

		#endregion

		#region Trigger

		private const string triggerSubmitFormat = "<input id=\"{0}\" type=\"submit\" value=\"{1}\"{2}/>";
		private const string triggerFormat = "<input id=\"{0}\" type=\"button\" value=\"{1}\"{2}/>";
		private const string triggerMinimalFormat = "<a id=\"{0}\" href=\"{1}\"{2}>{1}</a>";

		public string Trigger(ITrigger trigger) {
			var controlBody = new StringBuilder();

			var attributes = Hint(trigger);
			var format = triggerFormat;
			if (trigger.Actions.Any(a => a.Action == XFormAction.Send))
				format = triggerSubmitFormat;
			else if (trigger.Appearance == Appearance.Minimal)
				format = triggerMinimalFormat;

			controlBody.AppendFormat(format, trigger.FullID, trigger.Label, attributes);

			var control = Control(trigger, controlBody);
			return control;
		}

		#endregion
	}
}